//5.1.2
/**
 * @param {string} str1
 * @remark 如果参数为空（或者为undefined和null），Object()返回一个空对象。
 */
function first1(str1) {
    var obj = Object();
    // 等同于
    // var obj = Object(undefined);
    // var obj = Object(null);
    var obj1=Object(1);
    return {
        obj,
        obj1}
}

/**
 * @param {string} str2
 * @remark instanceof运算符用来验证，一个对象是否为指定的构造函数的实例。obj instanceof Object返回true，就表示obj对象是Object的实例。
 */
function first2(str2) {
    var obj = Object();
    return obj instanceof Object
}

/**
 * @param {string} str3
 * @remark 如果参数是原始类型的值，Object方法将其转为对应的包装对象的实例
 */
function first3(str3) {
    var obj = Object(1);
    return{
        return1:obj instanceof Object ,
        return2:obj instanceof Number}
}

/**
 * @param {string} str4
 * @remark 如果Object方法的参数是一个对象，它总是返回该对象，即不用转换。
 */
function first4(str4) {
    var arr = [];
    var obj = Object(arr);
    return obj === arr
}

/**
 * @param {string} str5
 * @remark 利用这一点，可以写一个判断变量是否为对象的函数
 */
function first5(str5) {
    function isObject(value) {
        return value === Object(value);
    }
    return {
        return1:isObject([]),
        return2:isObject(true)
    }
}

// console.log(first1());//{obj: {}, obj1: [Number: 1]}
// console.log(first2());//true
// console.log(first3());//{return1: true, return2: true}
// console.log(first4());//true
// console.log(first5());//{return1: true, return2: false}

//5.1.3
/**
 * @param {string} str6
 * @remark Object构造函数的用法与工具方法很相似，几乎一模一样。
 * @remark Object(value)与new Object(value)两者的语义是不同的，Object(value)表示将value转成一个对象，new Object(value)则表示新生成一个对象，它的值是value。
 */
function first6(str6) {
    var o1 = {a: 1};
    var o2 = new Object(o1);
    return o1 === o2 
}

// console.log(first6());//true

//5.1.4-1
/**
 * @param {string} str7
 * @remark Object.keys方法的参数是一个对象，返回一个数组
 */
function first7(str7) {
    var obj = {
        p1: 123,
        p2: 456
    };
    return Object.keys(obj)
}

/**
 * @param {string} str8
 * @remark Object.getOwnPropertyNames方法与Object.keys类似，也是接受一个对象作为参数，返回一个数组，包含了该对象自身的所有属性名。
 */
function first8(str8) {
    var obj = {
        p1: 123,
        p2: 456
    };
    return Object.getOwnPropertyNames(obj)
}

/**
 * @param {string} str9
 * @remark 只有涉及不可枚举属性时，才会有不一样的结果。Object.keys方法只返回可枚举的属性,Object.getOwnPropertyNames方法还返回不可枚举的属性名。
 * @remark 在JavaScript中，数组是一种特殊的对象，用于保存多个值在一个连续的内存空间中。数组的每个元素都通过一个数字索引来访问，这个索引从0开始，一直到数组的长度减1。
 * @remark 数组的length属性是不可枚举的属性，所以只出现在Object.getOwnPropertyNames方法的返回结果中。
 */
function first9(str9) {
    var arr = [1, 2];
    return {
        return1:Object.keys(arr),
        return2:Object.getOwnPropertyNames(arr)
    }
}

/**
 * @param {string} str10
 * @remark 由于 JavaScript 没有提供计算对象属性个数的方法，所以可以用这两个方法代替。
 */
function first10(str10) {
    var obj = {
        p1: 123,
        p2: 456
    };
    return {
        return1:Object.keys(obj).length ,
        return2:Object.getOwnPropertyNames(obj).length
    }
}

// console.log(first7());//["p1", "p2"]
// console.log(first8());//["p1", "p2"]
// console.log(first9());//{return1: ["0", "1"], return2: ["0", "1", "length"]}
// console.log(first10());//{return1: 2, return2: 2}

//5.1.5-2
/**
 * @param {string} str11
 * @remark valueOf方法的作用是返回一个对象的“值”，默认情况下返回对象本身。
 * @remark obj.valueOf()//{}
 */
function first11(str11) {
    var obj = new Object();
    return obj.valueOf() === obj 
}

/**
 * @param {string} str12
 * @remark valueOf方法的主要用途是，JavaScript 自动类型转换时会默认调用这个方法
 * @remark 将对象obj与数字1相加，这时 JavaScript 就会默认调用valueOf()方法，求出obj的值（[object Object]）再与1相加。
 */
function first12(str12) {
    var obj = new Object();
    return 1 + obj 
}

/**
 * @param {string} str13
 * @remark 自定义valueOf方法，就可以得到想要的结果。
 * @remark 这种方法就相当于用自定义的obj.valueOf，覆盖Object.prototype.valueOf。
 */
function first13(str13) {
    var obj = new Object();
    obj.valueOf = function() {
        return 1;
    };
    return 1 + obj 
}

// console.log(first11());//true
// console.log(first12());//"1[object Object]"
// console.log(first13());//2

//5.1.5-2
/**
 * @param {string} str14
 * @remark toString方法的作用是返回一个对象的字符串形式，默认情况下返回类型字符串。
 * @remark 对于一个对象调用toString方法，会返回字符串[object Object]，该字符串说明对象的类型。
 */
function first14(str14) {
    var o2 = {a:1};
    return o2.toString()
}

/**
 * @param {string} str15
 * @remark 字符串[object Object]本身没有太大的用处，但是通过自定义toString方法，可以让对象在自动类型转换时，得到想要的字符串形式。
 */
function first15(str15) {
    var obj = new Object();

    obj.toString = function () {
      return 'hello';
    };
    
    return obj + ' ' + 'world' 
}

/** 
 * @param {string} str16
 * @remark 数组、字符串、函数、Date 对象都分别部署了自定义的toString方法，覆盖了Object.prototype.toString方法。
 */
function first16(str16) {
    return{
        return1:[1, 2, 3].toString() ,
        return2:'hello world'.toString() ,
        return3:function () {return 123}.toString() ,
        return4:(new Date()).toString()
    }
}

// console.log(first14());//"[object Object]"
// console.log(first15());//"hello world"
// console.log(first16());//{return1: "1,2,3", return2: "hello world", return3: 'function () {return 123}',return4: 'Mon Nov 20 2023 16:05:18 GMT+0800 (中国标准时间)'}

//5.1.5-3
/**
 * @param {string} str17
 * @remark Object.prototype.toString方法返回对象的类型字符串，因此可以用来判断一个值的类型。
 */
function first17(str17) {
    return Object.prototype.toString.call()
}

/**
 * @param {string} str18
 * @remark 利用这个特性，可以写出一个比typeof运算符更准确的类型判断函数
 */
function first18(str18) {
    var type = function (obj) {
        return Object.prototype.toString.call(obj);
    };
    return{
        return1:type([]) ,
        return2:type('') ,
        return3:type(new Date()) ,
        return4:type(/test/) ,
        return5:type({}) 
    }
}

// console.log(first17());//[object undefined]
// console.log(first18());//{return1: "[object Array]", return2: "[object String]", return3: "[object Date]", return4: "[object RegExp]", return5: "[object Object]"}

//5.1.4
/**
 * @param {string} str19
 * @remark Object.prototype.toLocaleString方法与toString的返回结果相同，也是返回一个值的字符串形式。
 */
function first19(str19) {
    var obj = {};
    return  obj.toLocaleString(obj)
}

/**
 * @param {string} str20
 * @remark 这个方法的主要作用是留出一个接口，让各种不同的对象实现自己版本的toLocaleString，用来返回针对某些地域的特定的值。
 * @remark toString()方法返回对象的一般字符串形式，toLocaleString()方法返回本地的字符串形式。
 */
function first20(str20) {
    var person = {
        toString: function () {
          return 'Henry Norman Bethune';
        },
        toLocaleString: function () {
          return '白求恩';
        }
    };
    return{
        return1:person.toString(),
        return2:person.toLocaleString()
    }
}

/**
 * @param {string} str21
 * @remark 日期的实例对象的toString和toLocaleString返回值就不一样，而且toLocaleString的返回值跟用户设定的所在地域相关。
 */
function first21(str21) {
    var date = new Date();
    return{
        return1:date.toString(),
        return2:date.toLocaleString()
    }
}

// console.log(first19());//[object Object]
// console.log(first20());//hello
// console.log(first20());//{return1: "Henry Norman Bethune", return2: "白求恩"}
// console.log(first21());//{  return1: 'Mon Nov 20 2023 18:58:14 GMT+0800 (中国标准时间)', return2: '2023/11/20 18:58:14'}

//5.1.5
/**
 * @param {string} str22
 * @remark Object.prototype.hasOwnProperty方法接受一个字符串作为参数，返回一个布尔值，表示该实例对象自身是否具有该属性。
 * @remark 对象obj自身具有p属性，所以返回true。toString属性是继承的，所以返回false。
 */
function first22(str22) {
    var obj = {
        p: 123
    };
      
    return {
        return1:obj.hasOwnProperty('p'),
        return2:obj.hasOwnProperty('toString')
    }
}
// console.log(first22());//{return1: true, return2: false}

//5.2.2
/**
 * @param {string} str23
 * @remark 每个属性都有自己对应的属性描述对象，保存该属性的一些元信息。
 * @remark Object.getOwnPropertyDescriptor()方法可以获取属性描述对象。它的第一个参数是目标对象，第二个参数是一个字符串，对应目标对象的某个属性名。
 * @remark Object.getOwnPropertyDescriptor()方法获取obj.p的属性描述对象。
 */
function first23(str23) {
    var obj = { p: 'a' };
    return Object.getOwnPropertyDescriptor(obj, 'p')
}

/**
 * @param {string} str24
 * @remark Object.getOwnPropertyDescriptor()方法只能用于对象自身的属性，不能用于继承的属性。
 * @remark toString是obj对象继承的属性，Object.getOwnPropertyDescriptor()无法获取。
 */
function first24(str24) {
    var obj = { p: 'a' };
    return Object.getOwnPropertyDescriptor(obj, 'toString')
}

// console.log(first23());//{value: "a", writable: true, enumerable: true, configurable: true}
// console.log(first24());//undefined

//5.2.3
/**
 * @param {string} str25
 * @remark object.getOwnPropertyNames方法返回一个数组，成员是参数对象自身的全部属性的属性名，不管该属性是否可遍历。
 * @remark obj.p1是可遍历的，obj.p2是不可遍历的。Object.getOwnPropertyNames会将它们都返回。
 */
function first25(str25) {
    var obj = Object.defineProperties({}, {
        p1: { value: 1, enumerable: true },
        p2: { value: 2, enumerable: false }
    });
      
    return Object.getOwnPropertyNames(obj)
}

/**
 * @param {string} str26
 * @remark 跟Object.keys的行为不同，Object.keys只返回对象自身的可遍历属性的全部属性名。
 * @remark 数组自身的length属性是不可遍历的，Object.keys不会返回该属性。第二个例子的Object.prototype也是一个对象，所有实例对象都会继承它，它自身的属性都是不可遍历的。
 */
function first26(str26) {
    return {
        return1:Object.keys([]),
        return2:Object.getOwnPropertyNames([]) ,
        return3:Object.keys(Object.prototype) ,
        return4:Object.getOwnPropertyNames(Object.prototype)
    }
}

// console.log(first25());//["p1", "p2"]
// console.log(first26());
/*
{
  return1: [],
  return2: [ 'length' ],
  return3: [],
  return4: [
    'constructor',
    '__defineGetter__',
    '__defineSetter__',
    'hasOwnProperty',
    '__lookupGetter__',
    '__lookupSetter__',
    'isPrototypeOf',
    'propertyIsEnumerable',
    'toString',
    'valueOf',
    '__proto__',
    'toLocaleString'
  ]
}
*/

//5.2.4
/**
 * @param {string} str27
 * @remark Object.defineProperty()方法允许通过属性描述对象，定义或修改一个属性，然后返回修改后的对象.
 * @remark Object.defineProperty()方法定义了obj.p属性。由于属性描述对象的writable属性为false，所以obj.p属性不可写
 */
function first27(str27) {
    var obj = Object.defineProperty({}, 'p', {
        value: 123,
        writable: false,
        enumerable: true,
        configurable: false
      });
      
    obj.p=246;
    return obj.p
}

/**
 * @param {string} str28
 * @remark 如果一次性定义或修改多个属性，可以使用Object.defineProperties()方法。
 */
function first28(str28) {
    var obj = Object.defineProperties({}, {
        p1: { value: 123, enumerable: true },
        p2: { value: 'abc', enumerable: true },
        p3: { get: function () { return this.p1 + this.p2 },
          enumerable:true,
          configurable:true
        }
    }); 
    return {
        return1:obj.p1,
        return2:obj.p2,
        return3:obj.p3
    }
}

/**
 * @param {string} str29
 * @remark 一旦定义了取值函数get（或存值函数set），就不能将writable属性设为true，或者同时定义value属性，否则会报错
 */
function first29(str29) {
    var obj = {};

    Object.defineProperty(obj, 'p', {
      value: 123,
      get: function() { return 456; }
    });
    return obj.p
}

/**
 * @param {string} str30
 * @remark Object.defineProperty()和Object.defineProperties()参数里面的属性描述对象，writable、configurable、enumerable这三个属性的默认值都为false。
 */
function first30(str30) {
    var obj = {};
    return{
        return1:Object.defineProperty(obj, 'foo', {value:123}),
        return2:Object.getOwnPropertyDescriptor(obj, 'foo')
    }
}

// console.log(first27());//123
// console.log(first28());//{return1: 123, return2: "abc", return3: '123abc'}
// console.log(first29());//Invalid property descriptor. Cannot both specify accessors and a value or writable attribute,
// console.log(first30());
/*
{
  return1: {},
  return2: {
    value: 123,
    writable: false,
    enumerable: false,
    configurable: false
  }
}
*/

//5.2.5
/**
 * @param {string} str31
 * @remark propertyIsEnumerable()方法返回一个布尔值，用来判断某个属性是否可遍历。注意，这个方法只能用于判断对象自身的属性，对于继承的属性一律返回false。
 */
function first31(str31) {
    var obj = {};
    obj.p = 123;
    return {
        return1:obj.propertyIsEnumerable('p'),
        return2:obj.propertyIsEnumerable('toString')
    }
}
// console.log(first31());//{return1: true, return2: false}

//5.2.6-1
/**
 * @param {string} str32
 * @remark value属性是目标属性的值。
 */
function first32(str32) {
    var obj = {};
    obj.p = 123;
    return Object.getOwnPropertyDescriptor(obj, 'p').value
}

// console.log(first32());//123


//5.2.6-2
/**
 * @param {string} str33
 * @remark writable属性是一个布尔值，决定了目标属性的值（value）是否可以被改变。
 * @remark obj.a的writable属性是false。然后，改变obj.a的值，不会有任何效果。
 */
function first33(str33) {
    var obj = {};
    Object.defineProperty(obj, 'a', {
      value: 37,
      writable: false
    });
    obj.a = 77;
    return obj.a;
}

/**
 * @param {string} str34
 * @remark 正常模式下，对writable为false的属性赋值不会报错，只会默默失败。但是，严格模式下会报错，即使对a属性重新赋予一个同样的值。
 * @remark 严格模式，对obj.a任何赋值行为都会报错。
 */
function first34(str34) {
    'use strict';
    var obj = {};
    
    Object.defineProperty(obj, 'a', {
      value: 37,
      writable: false
    });
    
    obj.a = 37;
}

/**
 * @param {string} str35
 * @remark 原型对象的某个属性的writable为false，那么子对象将无法自定义这个属性
 * @remark proto是原型对象，它的foo属性不可写。obj对象继承proto，也不可以再自定义这个属性了。
 */
function first35(str35) {
    var proto = Object.defineProperty({}, 'foo', {
        value: 'a',
        writable: false
      });
      
    var obj = Object.create(proto);
    obj.foo = 'b';
    return obj.foo 
}

/**
 * @param {string} str36
 * @remark 有一个规避方法，就是通过覆盖属性描述对象，绕过这个限制。原因是这种情况下，原型链会被完全忽视。
 */
function first36(str36) {
    var proto = Object.defineProperty({}, 'foo', {
        value: 'a',
        writable: false
    });
      
    var obj = Object.create(proto);
    Object.defineProperty(obj, 'foo', {
        value: 'b'
    });
      
    return obj.foo
}
// console.log(first33());//37
// console.log(first34());//Uncaught TypeError: Cannot assign to read only property 'a' of object '#<Object>'
// console.log(first35());//a
// console.log(first36());//b

//5.2.6-3
/**
 * @param {string} str37
 * @remark enumerable（可遍历性）返回一个布尔值，表示目标属性是否可遍历。
 * @remark enumerable可以用来设置“秘密”属性。
 * @remark 由于 x 属性的 enumerable 属性被设置为 false，这意味着 x 属性不会被枚举，因此这个循环不会打印出任何东西。
 * @reamrk Object.keys(obj) ,即一个包含 obj 所有可枚举属性名称的数组；
 * @remark JSON.stringify(obj) ，即将 obj 转换为 JSON 字符串。
 */
function first37(str37) {
    var obj = {};

    Object.defineProperty(obj, 'x', {
        value: 123,
        enumerable: false
    });

    console.log(obj.x) 

    for (var key in obj) {
        console.log(key);
    }

    return{
        return1:Object.keys(obj) ,
        return2:JSON.stringify(obj)
    }
}

// console.log(first37());//123  { return1: [], return2: '{}' }

//5.2.6-4
/**
 * @param {string} str38
 * @remark configurable(可配置性）返回一个布尔值，决定了是否可以修改属性描述对象。也就是说，configurable为false时，writable、enumerable和configurable都不能被修改了。
 */
function first38(str38) {
    var obj = Object.defineProperty({}, 'p', {
        value: 1,
        writable: false,
        enumerable: false,
        configurable: false
    });
    return Object.defineProperty(obj, 'p', {writable: true})
}

/**
 * @param {string} str39
 * @remark writable属性只有在false改为true时会报错，true改为false是允许的。
 */
function first39(str39) {
    var obj = Object.defineProperty({}, 'p', {
        value: 1,
        writable: true,
        enumerable: false,
        configurable: false
    });
    
    Object.defineProperty(obj, 'p', {writable: false})
    return obj.p
}

/**
 * @param {string} str40
 * @remark value属性的情况比较特殊。只要writable和configurable有一个为true，就允许改动value。
 */
function first40(str40) {
    var obj = Object.defineProperty({}, 'p', {
        value: 1,
        writable: false,
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(obj, 'p', {value: 2})
    return obj.p
}

/**
 * @param {string} str41
 * @remark 可配置性决定了目标属性是否可以被删除（delete）
 */
function first41(str41) {
    var obj = Object.defineProperties({}, {
        p1: { value: 1, configurable: true },
        p2: { value: 2, configurable: false }
      });
      
    delete obj.p1 // true
    delete obj.p2 // false
      
    return{
        return1:obj.p1 ,
        return2:obj.p2
    }
}

// console.log(first38());//Uncaught TypeError: Cannot redefine property: p
// console.log(first39());//1
// console.log(first40());//2
// console.log(first41());//{ return1: undefine, return2: 2 }

//5.2.7
/**
 * @param {string} str42
 * @remark 除了直接定义以外，属性还可以用存取器（accessor）定义。
 */
function first42(str42) {
    var obj = Object.defineProperty({}, 'p', {
        get: function () {
          return 'getter';
        },
        set: function (value) {
          console.log('setter: ' + value);
        }
    });
      
    return {
        return1 :obj.p,
        return2 :obj.p = 1
    } 
}

/**
 * @param {string} str43
 * @remark 存取器往往用于，属性的值依赖对象内部数据的场合。
 */
function first43(str43) {
    var obj ={
        $n : 5,
        get next() { return this.$n++ },
        set next(n) {
          if (n >= this.$n) this.$n = n;
          else throw new Error('新的值必须大于当前值');
        }
    };
      
    return obj.next
}
// console.log(first42());
/*
setter: 1
{ return1: 'getter', return2: 1 }
*/
// console.log(first43());//5

//5.2.8
/**
 * @param {string} str44
 * @remark 有时，我们需要将一个对象的所有属性，拷贝到另一个对象，可以用下面的方法实现。
 */
function first44(str44) {
    var extend = function (to, from) {
        for (var property in from) {
          to[property] = from[property];
        }
      
        return to;
    }
      
    return extend({}, {
        a: 1
    })
}

/**
 * @param {string} str45
 * @remark 上面这个方法的问题在于，如果遇到存取器定义的属性，会只拷贝值。
 * @remark 为了解决这个问题，我们可以通过Object.defineProperty方法来拷贝属性。
 */
function first45(str45) {
    var extend = function (to, from) {
        for (var property in from) {
          if (!from.hasOwnProperty(property)) continue;
          Object.defineProperty(
            to,
            property,
            Object.getOwnPropertyDescriptor(from, property)
          );
        }
      
        return to;
      }
      
      extend({}, { get a(){ return 1 } })
      // { get a(){ return 1 } })
}

// console.log(first44());//{ a: 1 }

//5.2.9-1
/**
 * @param {string} str45
 * @remark Object.preventExtensions方法可以使得一个对象无法再添加新的属性。
 */
function first45(str45) {
    var obj = new Object();
    Object.preventExtensions(obj);

    Object.defineProperty(obj, 'p', {
        value: 'hello'
    });

    obj.p = 1;
    return obj.p
}

// console.log(first45());//Uncaught TypeError: Cannot define property:p, object is not extensible

//5.2.9-2
/**
 * @param {string} str46
 * @remark Object.isExtensible方法用于检查一个对象是否使用了Object.preventExtensions方法。也就是说，检查是否可以为一个对象添加属性。
 * @remark 调用了Object.preventExtensions(obj)。这个方法让一个对象不再可以被扩展，也就是说，阻止了新的属性被添加到这个对象中。所以，在这个操作之后，Object.isExtensible(obj)的返回值就变成了false，表示这个对象现在不可以被扩展了。
 */
function first46(str46) {
    var obj = new Object();
    function   f1(){
        return Object.isExtensible(obj)
    }
    function   f2(){
        Object.preventExtensions(obj);
        return Object.isExtensible(obj)
    }
    return{
        f1: f1(),
        f2: f2()
    }
}

// console.log(first46());//{ f1: true, f2: false }

//5.2.9-3
/**
 * @param {string} str47
 * @remark Object.seal方法使得一个对象既无法添加新属性，也无法删除旧属性。
 */
function first47(str47) {
    var obj = { p: 'hello' };
    Object.seal(obj);

    delete obj.p;
    obj.x = 'world';
    return {
        return1:obj.p,
        return2:obj.x
    }    
}

/**
 * @param {string} str48
 * @remark Object.seal实质是把属性描述对象的configurable属性设为false，因此属性描述对象不再能改变了。
 * @remark 使用Object.seal方法之后，属性描述对象的configurable属性就变成了false，然后改变enumerable属性就会报错。
 */
function first48(str48) {
    var obj = {
        p: 'a'
    };
    function f1(){
        return Object.getOwnPropertyDescriptor(obj, 'p')
    }
    function f2(){
        Object.seal(obj);
        return Object.getOwnPropertyDescriptor(obj, 'p')
    }
    return{
        f1: f1(),
        f2: f2(),
        return: Object.defineProperty(obj, 'p', {
            enumerable: false
        })
    }
}

/**
 * @param {string} str49
 * @remark Object.seal只是禁止新增或删除属性，并不影响修改某个属性的值。
 */
function first49(str49) {
    var obj = { p: 'a' };
    Object.seal(obj);
    obj.p = 1;
    return obj.p
}

// console.log(first47());//{ return1: 'hello', return2: undefined }
// console.log(first48());
/*
{
 f1: { value: 'a', writable: true, enumerable: true, configurable: true },
 f2: { value: 'a', writable: true, enumerable: true, configurable: false }
}
 result : TypeError: Cannot redefine property: p
*/
// console.log(first49());//1

//5.2.9-4
/**
 * @param {string} str50
 * @remark Object.isSealed方法用于检查一个对象是否使用了Object.seal方法。Object.isExtensible方法也返回false。
 */
function first50(str50) {
    var obj = { p: 'a' };

    Object.seal(obj);
    return {
        return1:Object.isSealed(obj),
        return2O:bject.isExtensible(obj) 
    }
}

// console.log(first50());//{ return1: true, return2O: false }

//5.2.9-5
/**
 * @param {string} str51
 * @remark Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值，使得这个对象实际上变成了常量。
 */
function first51(str51) {
    var obj = {
        p: 'hello'
    };
      
    Object.freeze(obj);
      
    obj.p = 'world';
    return {
        return1:obj.p,
        return2:delete obj.p
    }
}

// console.log(first51());//{ return1: 'hello', return2: false }

//5.2.9-6
/**
 * @param {string} str52
 * @remark Object.isFrozen方法用于检查一个对象是否使用了Object.freeze方法。
 * @remark 使用Object.freeze方法以后，Object.isSealed将会返回true，Object.isExtensible返回false。
 */
function first52(str52) {
    var obj = {
        p: 'hello'
    };
      
    Object.freeze(obj);
    return {
        return1:Object.isFrozen(obj) ,
        return2:Object.isSealed(obj),
        return3:Object.isExtensible(obj)
    }
}

/**
 * @param {string} str53
 * @remark Object.isFrozen的一个用途是，确认某个对象没有被冻结后，再对它的属性赋值。
 */
function first53(str53) {
    var obj = {
        p: 'hello'
    };  
      
    Object.freeze(obj);
      
    if(!Object.isFrozen(obj)) {
        obj.p = 'world';
    }
    return obj.p;
}

// console.log(first52());//{ return1: true, return2: true, return3: false }
// console.log(first53());//hello

//5.2.9-7
/**
 * @param {string} str54
 * @remark 上面的三个方法锁定对象的可写性有一个漏洞：可以通过改变原型对象，来为对象增加属性。
 * @remark 对象obj本身不能新增属性，但是可以在它的原型对象上新增属性，就依然能够在obj上读到。
 */
function first54(str54) {
    var obj = new Object();
    Object.preventExtensions(obj);

    var proto = Object.getPrototypeOf(obj);
    proto.t = 'hello';
    return obj.t
}

/**
 * @param {string} str54
 * @remark 一种解决方案是，把obj的原型也冻结住
 */
function first54(str54) {
    var obj = new Object();
    Object.preventExtensions(obj);
    
    var proto = Object.getPrototypeOf(obj);
    Object.preventExtensions(proto);
    
    proto.t = 'hello';
    return obj.t
}

/**
 * @param {string} str55
 * @remark 另外一个局限是，如果属性值是对象，上面这些方法只能冻结属性指向的对象，而不能冻结对象本身的内容。
 * @remark obj.bar属性指向一个数组，obj对象被冻结以后，这个指向无法改变，即无法指向其他值，但是所指向的数组是可以改变的。
 */
function first55(str55) {
    var obj = {
        foo: 1,
        bar: ['a', 'b']
    };
    Object.freeze(obj);
      
    obj.bar.push('c');
    return obj.bar 
}

// console.log(first54());//hello
// console.log(first54());//undefined
// console.log(first55());//[ 'a', 'b' , 'c' ]

//5.3.1
/**
 * @param {string} str56
 * @remark 单个正整数参数，表示返回的新数组的长度,so ,a是Array()生成的一个长度为3的空数组
 * @remark 如果参数是一个正整数，返回数组的成员都是空位。虽然读取的时候返回undefined，但实际上该位置没有任何值。虽然这时可以读取到length属性，但是取不到键名。
 * @remark a的键名（成员的序号）都是空的，b的键名是有值的。
 */
function first56(str56) {
    var a = new Array(3);
    var b = [undefined, undefined, undefined];
    
    return{
        s1:a.length,
        s2:b.length,
    
        s3:a[0],
        s4:b[0],
    
        s4:0 in a,
        s5:0 in b
    }
}
// console.log(first56());//{ s1: 3, s2: 3, s3: undefined, s4: false, s5: true}

//5.3.2-1
/**
 * @param {string}str67
 * @ remark Array.isArray方法返回一个布尔值，表示参数是否为数组。它可以弥补typeof运算符的不足。
 */
function first67(str67) {
    var a = [1, 2];
    return Array.isArray(a);
}
// console.log(first67());//true

//5.3.3-1
/**
 * @param {string}str68
 * @remark 数组的valueOf方法返回数组本身。数组的toString方法返回数组的字符串形式。
 */
function first68(str68) {
    var a = [1, 2, 3, [4, 5, 6]];
      
    return{
        s1:a.valueOf(),
        s2:a.toString()
    }
}
// console.log(first68());//{ s1: [ 1, 2, 3, [ 4, 5, 6 ] ], s2: '1,2,3,4,5,6' }

//5.3.3-2
/**
 * @param {string}str69
 * @remark push方法用于在数组的末端添加一个或多个元素，并返回添加新元素后的数组长度。pop方法用于删除数组的最后一个元素，并返回该元素。注意，两方法会改变原数组。
 */
function first69(str69) {
    var a = [1,2,3,4,5];
    a.push('a');//此时a:[1,2,3,4,5,'a']
    a.pop()//此时a:[1,2,3,4,5]
      
    return a
}

/**
 * @param {string}str70
 * @remark 对空数组使用pop方法，不会报错，而是返回undefined。
 */
function first70(str70) {
    var a = [];
      
    return a.pop()
}

// console.log(first69());//[ 1, 2, 3, 4, 5 ]
// console.log(first70());//undefined

//5.3.3-3
/**
 * @param {string}str71
 * @remark shift()方法用于删除数组的第一个元素，并返回该元素。注意，该方法会改变原数组。
 * @remark shift()方法可以遍历并清空一个数组。
 */
function first71(str71) {
    var list = [1, 2, 3, 4];
    var item;
    
    while (item = list.shift()) {
      console.log(item);
    }
    
    return list //[]
}

/**
 * @param {string}str72
 * @remark unshift()方法用于在数组的第一个位置添加元素，并返回添加新元素后的数组长度。注意，该方法会改变原数组。
 * @remark unshift()方法可以接受多个参数，这些参数都会添加到目标数组头部。
 */
function first72(str72) {
    var arr = [ 'c', 'd' ];
    arr.unshift('a', 'b') 
    return arr
}
// console.log(first71());//1, 2, 3, 4,[] 
// console.log(first72());//[ 'a', 'b', 'c', 'd' ]

//5.3.3-4
/**
 * @remark join()方法以指定参数作为分隔符，将所有数组成员连接为一个字符串返回。如果不提供参数，默认用逗号分隔。
 * @remark 如果数组成员是undefined或null或空位，会被转成空字符串。
 */
function first73(str73) {
    return{
        s1:[undefined, null].join('#'),
        s2:['a',, 'b'].join('-')
    }
}

/**
 * @param {string}str74
 * @remark 通过call方法，这个方法也可以用于字符串或类似数组的对象
 */
function first74(str74) {
    var obj = { 0: 'a', 1: 'b', length: 2 };
    return Array.prototype.join.call(obj, '-')
}
// console.log(first73());//{ s1: '#', s2: 'a--b' }
// console.log(first74());//'a-b'

//5.3.3-5
/**
 * @param {string}str75
 * @remark concat方法用于多个数组的合并。它将新数组的成员，添加到原数组成员的后部，然后返回一个新数组，原数组不变。
 * @remark 除了数组作为参数，concat也接受其他类型的值作为参数，添加到目标数组尾部。
 */
function first75(str75) {
    var a = [1, 2];
      
    return{
        s1:a.concat([3,4]),
        s2:a.concat('c','d')
    }
}

/**
 * @param {string}str76
 * @remark 如果数组成员包括对象，concat方法返回当前数组的一个浅拷贝。所谓“浅拷贝”，指的是新数组拷贝的是对象的引用。
 */
function first76(str76) {
    var obj = { a: 1 };
    var oldArray = [obj];

    var newArray = oldArray.concat();
    
    // return newArray//[{a:1}]
    obj.a = 2;
    return newArray[0].a
}
// console.log(first75());//{ s1: [ 1, 2, 3, 4 ], s2: [ 1, 2, 'c', 'd' ] }
// console.log(first76());//2

//5.3.3-6
/**
 * @param {string}str77
 * @remark reverse方法用于颠倒排列数组元素，返回改变后的数组。注意，该方法将改变原数组。
 */
function first77(str77) {
    var a = [1, 2, 3];
      
    return a.reverse()
}
// console.log(first77());//[ 3, 2, 1 ]

//5.3.3-7
/**
 * @param {string}str78
 * @remark slice()方法用于提取目标数组的一部分，返回一个新数组，原数组不变。
 * @remark 如果省略第二个参数，则一直返回到原数组的最后一个成员。
 * @remark slice()方法的参数是负数，则表示倒数计算的位置。
 * @remark 如果第一个参数大于等于数组长度，或者第二个参数小于第一个参数，则返回空数组。
 */
function first78(str78) {
    var a = [1, 2, 3];
      
    return{
        s1:a.slice(1),
        s2:a.slice(),
        s3:a.slice(1, 2),
        s4:a.slice(-1),//倒数第一位从左到右取到最后
        s5:a.slice(-2, -1),
        s6:a.slice(5),
        s7:a.slice(1,-1),//第二位取到倒数第一位
        s8:a.slice(3, 1),
    }
}

/**
 * @param {string}str79
 * @remark slice()方法的一个重要应用，是将类似数组的对象转为真正的数组。
 */
function first79(str79) {
    var obj = { 0: 'a', 1: 'b', length: 2 };
      
    return Array.prototype.slice.call(obj);
}
// console.log(first78());
/*
 {
    s1: [ 2, 3 ],
    s2: [ 1, 2, 3 ],
    s3: [ 2 ],
    s4: [ 3 ],
    s5: [ 2 ],
    s6: [],
    s7: [ 2 ],
    s8: []
 }
 */
// console.log(first79());//[ 'a', 'b' ]


//5.3.3-8
/**
 * @param {string}str80
 * @remark splice()方法用于删除原数组的一部分成员，并可以在删除的位置添加新的数组成员，返回值是被删除的元素。注意，该方法会改变原数组。
 * @remark 起始位置如果是负数，就表示从倒数位置开始删除。
 * @remark 如果只是单纯地插入元素，splice方法的第二个参数可以设为0。
 * @remark 如果只提供第一个参数，等同于将原数组在指定位置拆分成两个数组。
 */
function first80(str80) {
    function f1(){
        var a = ['a', 'b', 'c', 'd', 'e', 'f']
        a.splice(4, 1, 1, 2)
        return a
    }
    function f2(){
        var b = ['a', 'b', 'c', 'd', 'e', 'f']
        b.splice(-4, 2) 
        return b
    }
    function f3(){
        var c = ['a', 'b', 'c', 'd', 'e', 'f']
        c.splice(1, 0, 2) 
        return c
    }
    function f4(){
        var d = ['a', 'b', 'c', 'd', 'e', 'f']
        d.splice(2) //从第三个元素的左边开始切，变成[a,b],[c,d,e,f],并舍弃后面那个数组
        return d
    }
    return{
        s1:f1(),
        s2:f2(),
        s3:f3(),
        s4:f4()
    }
}
// console.log(first80());
/*
{
  s1: [
    'a', 'b', 'c', 'd',
    1,   2,   'f'
  ],
  s2: [ 'a', 'b', 'e', 'f' ],
  s3: [
    'a', 2,   'b',
    'c', 'd', 'e',
    'f'
  ],
  s4: [ 'a', 'b' ]
}
*/

//5.3.3-9
/**
 * @param {string}str81
 * @remark sort方法对数组成员进行排序，默认是按照字典顺序排序。排序后，原数组将被改变。
 * @remark 如果想让sort方法按照自定义方式排序，可以传入一个函数作为参数
 * @remark return a - b; 表示按照从小到大的顺序排序。
 */
function first81(str81) {
    return [10111, 1101, 111].sort(function (a, b) {
        return a - b;
    })
    
}

// console.log(first81());//[ 111, 1101, 10111 ]


//5.3.3-10
/**
 * @param {string}str82
 * @remark map()方法将数组的所有成员依次传入参数函数，然后把每一次的执行结果组成一个新数组返回，原数组没有变化。
 * @remark map()方法接受一个函数作为参数。该函数调用时，map()方法向它传入三个参数：当前成员、当前位置和数组本身。
 */
function first82(str82) {
    var a = [1, 2, 3];
      
    return a.map(function (item, index) {
        return item + index;
    })
}

/**
 * @param {string}str82
 * @remark map()方法还可以接受第二个参数，用来绑定回调函数内部的this变量
 */
function first82(str82) {
    var a = [1, 2, 3];
      
    return a.map(function (item, index) {
        return item + index;
    }, this)
}

/**
 * @param {string}str82
 * @remark map()方法还可以接受第二个参数，用来绑定回调函数内部的this变量
 * @remark 只有两个参数，map省略elem(当前成员的值)，只有一个参数，map保留elem
 */
function  first82(str82) {
    var arr = ['a', 'b', 'c'];

    return [1, 2].map(function (e) {
      return this[e];
    }, arr)
}

/**
 * @param {string}str82
 * @remark 如果数组有空位，map()方法的回调函数在这个位置不会执行，会跳过数组的空位。
 * @remark map()方法不会跳过undefined和null，但是会跳过空位。
 */
function first82(str82) {
    var f = function (n) { return 'a' };

    return{
        s1:[1, undefined, 2].map(f) ,
        s2:[1, null, 2].map(f) ,
        s3:[1, , 2].map(f) 
    }
}
// console.log(first82());//[ 1, 3, 5 ]
// console.log(first82());//[ 'b', 'c' ]
// console.log(first82());
/*
{
  s1: [ 'a', 'a', 'a' ],
  s2: [ 'a', 'a', 'a' ],
  s3: [ 'a', <1 empty item>, 'a' ]
}
*/

//5.3.3-11
/**
 * @param {string}str83
 * @remark forEach()的用法与map()方法一致，参数是一个函数，该函数同样接受三个参数：当前值、当前位置、整个数组。
 */
function first83(str83) {
    function log(element, index, array) {
        console.log('[' + index + '] = ' + element);
      }
      
    return[2, 5, 9].forEach(log);
}

/**
 * @param {string}str84
 * @param forEach()方法也可以接受第二个参数，绑定参数函数的this变量。
 */
function first84(str84) {
    var out = [];

    [1, 2, 3].forEach(function(elem) {
      this.push(elem * elem);
    }, out);
    
    return out 
}

/**
 * @param {string}str85
 * @remark forEach()方法无法中断执行，总是会将所有成员遍历完。如果希望符合某种条件时，就中断遍历，要使用for循环。
 */
function first85(str85) {
    var arr = [1, 2, 3];

    for (var i = 0; i < arr.length; i++) {
      if (arr[i] === 2) break;
      console.log(arr[i]);
    }
}

/**
 * @param {string}str86
 * @remark forEach()方法也会跳过数组的空位。
 */
function first86(str86) {
    var log = function (n) {
        console.log(n + 1);
      };
      
    return {
        s1:[1, , 2].forEach(log),//2,3
        s2:[1, undefined, 2].forEach(log),//2,NaN,3
        s3:[1, null, 2].forEach(log)//2,1,3
    }
}
// console.log(first83());
/*
[0] = 2
[1] = 5
[2] = 9
undefined
*/
// console.log(first84());//[ 1, 4, 9 ]
// console.log(first85());//1  undefined
// console.log(first86());//{ s1:undefined ,s2: undefined ,s3: undefined }

//5.3.3.12
/**
 * @param {string}str87
 * @remark filter()方法用于过滤数组成员，满足条件的成员组成一个新数组返回。
 * @remark filter()方法的参数函数可以接受三个参数：当前成员，当前位置和整个数组。
 */
function first87(str87) {
    var arr = [1, 2, 3, 4, 5];

    return arr.filter(function (elem, index, array) {
      return index % 2 === 0;;
    });
}

/**
 * @param {string}str88
 * @remark filter()方法还可以接受第二个参数，用来绑定参数函数内部的this变量。
 * @remark 过滤器myFilter()内部有this变量，它可以被filter()方法的第二个参数obj绑定，返回大于3的成员。
 */
function first88(str88) {
    var obj = { MAX: 3 };
    var myFilter = function (item) {
      if (item > this.MAX) return true;
    };
    
    var arr = [2, 8, 3, 4, 1, 3, 2, 9];
    return arr.filter(myFilter, obj)
}
// console.log(first87());//[ 1, 3, 5 ]
// console.log(first88());//[ 8, 4, 9 ]

//5.3.3-13
/**
 * @param {string}str89
 * @remark 它们接受一个函数作为参数，所有数组成员依次执行该函数。该函数接受三个参数：当前成员、当前位置和整个数组，然后返回一个布尔值。
 * @remark some方法是只要一个成员的返回值是true，则整个some方法的返回值就是true，否则返回false。
 */
function first89(str89) {
    var arr = [1, 2, 3];

    return arr.some(function (elem, index, array) {
      return elem === 2;
    });
}

/**
 * @param {string}str90
 * @remark every方法是所有成员的返回值都是true，整个every方法才返回true，否则返回false。
 */
function first90(str90) {
    var arr = [-1, 2, 3];

    return arr.every(function (elem, index, array) {
      return elem > 0;
    });
}

/**
 * @param {string}str91
 * @remark 对于空数组，some方法返回false，every方法返回true，回调函数都不会执行。
 */
function first91(str91) {
    var arr = [];

    return arr.some(function (elem, index, array) {
      console.log('some');
    }) && arr.every(function (elem, index, array) {
      console.log('every');
    });
}

// console.log(first89());//true
// console.log(first90());//false
// console.log(first91());//false

//5.3.3-14
/**
 * @param {string}str92
 * @remark reduce()是从左到右处理（从第一个成员到最后一个成员），reduceRight()则是从右到左（从最后一个成员到第一个成员），其他完全一样。
 */
function first92(str92) {
    var arr = [1, 2, 3];

    return arr.reduce(function (prev, elem, index, array) {
      console.log(elem);
      return prev + elem;
    });
}

/**
 * @param {string}str93
 * @remark 如果要对累积变量指定初值，可以把它放在reduce()方法和reduceRight()方法的第二个参数。
 */
function first93(str93) {
    var arr = [1, 2, 3];

    return arr.reduceRight(function (prev, elem, index, array) {
      console.log(elem);
      return prev + elem;
    }, 10);
}

/**
 * @param {string}str94
 * @remark 第二个参数可以防止空数组报错。
 */
function first94(str94) {
    function add(prev, cur) {
        return prev + cur;
    }
    return [].reduce(add, 1)
}

/**
 * @param {string}str95
 * @remark 字符长度最长的数组成员。
 */
function first95(str95) {
    function findLongest(entries) {
        return entries.reduce(function (longest, entry) {
          return entry.length > longest.length ? entry : longest;
        }, '');
    }
      
    return findLongest(['aaa', 'bb', 'c'])
}

// console.log(first92());//2  3  6
// console.log(first93());//3 2 1 16
// console.log(first94());//1
// console.log(first95());//'aaa'

//5.3.3-15
/**
 * @param {string}str96
 * @remark indexOf方法返回给定元素在数组中第一次出现的位置，如果没有出现则返回-1。
 * @remark indexOf方法还可以接受第二个参数，表示搜索的开始位置。
 */
function first96(str96) {
    var arr = [1, 2, 3];

    return arr.indexOf(2,1);
}

/**
 * @param {string}str97
 * @remark lastIndexOf方法返回给定元素在数组中最后一次出现的位置，如果没有出现则返回-1。
 */
function first97(str97) {
    var arr = [1, 2, 3];
    return arr.lastIndexOf(2);
}

/**
 * @param {string}str98
 * @remark 这两个方法不能用来搜索NaN的位置，即它们无法确定数组成员是否包含NaN。
 * @remark 因为这两个方法内部，使用严格相等运算符（===）进行比较，而NaN是唯一一个不等于自身的值。
 */
function first98(str98) {
    var arr = [1, 2, NaN];
    return{
        return1:arr.indexOf(NaN),
        return2:arr.lastIndexOf(NaN)
    }
}

// console.log(first96());//1
// console.log(first97());//1
// console.log(first98());//{return1:-1, return2:-1}

//5.3.4-16
/**
 * @param {string}str99
 * @remark 面这些数组方法之中，有不少返回的还是数组，所以可以链式使用
 * @remark 返回的是新数组，不影响原数组
 */
function    first99(str99) {
    var arr = [1, 2, 3];
    return arr.map(function (elem) {
      return elem +1;
    }).filter(function (elem) {
      return elem % 2 === 0;
    });
}
// console.log(first99());//[2, 4]

//5.4.1
/**
 * @param {string}str100
 * @remark 所谓“包装对象”，指的是与数值、字符串、布尔值分别相对应的Number、String、Boolean三个原生对象。这三个原生对象可以把原始类型的值变成（包装成）对象。
 */
function first100(str100) {
    var num = 123;
    return{
        num:typeof(new Number(num)),
        str:typeof(new String(num))
    }
}
// console.log(first100());//{num:'object', str:'object'}

//5.4.3
/**
 * @param {string}str101
 * @remark 原始类型的值会自动当作包装对象调用，即调用包装对象的属性和方法。这时，JavaScript 引擎会自动将原始类型的值转为包装对象实例，并在使用后立刻销毁实例。
 */
function first101(str101) {
    var str = 'abc';
    return str.length 

    // 等同于
    // var strObj = new String(str)
    // String {
    //   0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"
    // }
    // strObj.length // 3
}

/**
 * @param {string}str102
 * @remark 自动转换生成的包装对象是只读的，无法修改。所以，字符串无法添加新属性。
 */
function first102(str102) {
    var s = 'Hello World';
    s.x = 123;
    return s.x
}
// console.log(first101());//3
// console.log(first102());//undefined

//5.4.4
/**
 * @param {string}str103
 * @remark 除了原生的实例方法，包装对象还可以自定义方法和属性，供原始类型的值直接调用。
 */
function first103(str103) {
    var str = 'abc';
    String.prototype.hello = function () {
      console.log('Hello, ' + this);
    };
    return str.hello();
}
// console.log(first103());//Hello, abc   undeifned

//5.5.1
/**
 * @param {string}str104
 * @remark Boolean对象主要用于生成布尔值的包装对象实例。
 */
function first104(str104) {
    var b = new Boolean(true);
    return typeof b 
}

/**
 * @param {string}str105
 * @remark false对应的包装对象实例，布尔运算结果也是true。
 */
function first105(str105) {
    if (new Boolean(false)) {
        console.log('true');
      } 
}
// console.log(first104());//object
// console.log(first105());//true   undeifned

//5.5.2
/**
 * @param {string}str106
 * @remark Boolean对象,单独使用，将任意值转为布尔值。
 * @remark 使用双重的否运算符（!）也可以将任意值转为对应的布尔值。
 */
function first106(str106) {
    var b = new Boolean(false);
    return{
        b1:!!b,
        b2:!b
    }
}

/**
 * @param {string}str107
 * @remark 对于一些特殊值，Boolean对象前面加不加new，会得到完全相反的结果，必须小心。
 */
function first107(str107) {
    if (Boolean(false)) {
        console.log('1');
    } 
    if (new Boolean(false)) {
        console.log('2');
    } 
}
// console.log(first106());//{ b1: true, b2: false }
// console.log(first107());//  2  undeifned

//5.6.1
/**
 * @param {string}str108
 * @remark 作为构造函数时，它用于生成值为数值的对象。
 * @remark 作为工具函数时，它可以将任何类型的值转为数值。
 */
function first108(str108) {
    var num = new Number(123);
    return {
        return1:typeof num,
        return2:Number(true)
    } 
}

// console.log(first108());//{ return1: 'object', return2: 1 }

//5.6.3-1
/**
 * @param {string}str109
 * @remark Number对象部署了自己的toString方法，用来将一个数值转为字符串形式
 * @remark toString方法可以接受一个参数，表示输出的进制
 */
function first109(str109) {
    return (10).toString(2)
}

/**
 * @function first109
 * @remark 10一定要放在括号里，这样表明后面的点表示调用对象属性。如果不加括号，这个点会被 JavaScript 引擎解释成小数点，从而报错。
 * @remark 除了为10加上括号，还可以在10后面加两个点
 */
function first109(str109) {
    return 10..toString(2);
}

/**
 * @param {string}str109
 * @remark 通过方括号运算符也可以调用toString方法。
 */
function first109(str109) {
    return 10['toString'](2) 
}
// console.log(first109());//1010  
// console.log(first109());//1010
// console.log(first109());//1010

//5.6.3-2
/**
 * @param {string}str110
 * @remark toFixed()方法先将一个数转为指定位数的小数，然后返回这个小数对应的字符串。
 * @remark 由于浮点数的原因，小数5的四舍五入是不确定的，使用的时候必须小心
 */
function first110(str110) {
    return{
        return1:(10).toFixed(2),
        return2:(10.055).toFixed(2) 
    }
}
// console.log(first110());//{ return1: '10.00', return2: '10.05'

//5.6.3-3
/**
 * @param {string}str111
 * @remark toExponential方法用于将一个数转为科学计数法形式。
 * @remark toExponential方法的参数是小数点后有效数字的位数，范围为0到100，超出这个范围，会抛出一个 RangeError 错误。
 */
function first111(str111) {
    return{
        return1:(10).toExponential(),
        return2:(10.055).toExponential(3) 
    }
}
// console.log(first111());//{ return1: '1e+1', return2: '1.055e+1'}

//5.6.3-4
/**
 * @param {string}str112
 * @remark Number.prototype.toPrecision()方法用于将一个数转为指定位数的有效数字。
 */
function first112(str112) {
    return{
        return1:(10).toPrecision(2),
        return2:(10.055).toPrecision(3) 
    }
}
// console.log(first112());//{ return1: '10', return2: '10.1' }

//5.6.3-5
/**
 * @param {string}str113
 * @remark Number.prototype.toLocaleString()方法接受一个地区码作为参数，返回一个字符串，表示当前数字在该地区的当地书写形式。
 * @remark 该方法还可以接受第二个参数配置对象，用来定制指定用途的返回字符串。
 */
function first113(str113) {
    return{
        return1:(10).toLocaleString(),
        return2:(10.055).toLocaleString('en-US', {style:'currency', currency:'USD'}) 
    }
}
// console.log(first113());//{ return1: '10', return2: '$10.06' }

//5.6.4
/**
 * @param {string}str114
 * @remark Number.prototype对象上面可以自定义方法，被Number的实例继承。
 */
function first114(str114) {
    Number.prototype.add = function (x) {
        return this + x;
      };
      
    return 8['add'](2)
}

/**
 * @param {string}str115
 * @remark 数值的自定义方法，只能定义在它的原型对象Number.prototype上面，数值本身是无法自定义属性的。
 */
function first115(str115) {
    var n = 1;
    n.x = 1;
    return n.x 
}
// console.log(first114());//10
// console.log(first115());//undefined

//5.7.1
/**
 * @param {string}str116
 * @remark 字符串对象是一个类似数组的对象（很像数组，但不是数组）。
 * @remark 除了用作构造函数，String对象还可以当作工具方法使用，将任意类型的值转为字符串。
 */
function first116(str116) {
    var str = new String(123);
    return {
        return1:typeof str,
        return2:String(true) 
    }
}
// console.log(first116());//{ return1: 'object', return2: 'true' }

//5.7.2-1
/**
 * @param {string}str117
 * @remark String.fromCharCode()方法用于将 Unicode 码点转为字符串。
 */
function first117(str117) {
    return String.fromCharCode(97, 98, 99)
}
/**
 * @param {string}str117
 * @remark ，该方法不支持 Unicode 码点大于0xFFFF的字符，即传入的参数不能大于0xFFFF（即十进制的 65535）
 */
function first117(str117) {
    return {
        return1:String.fromCharCode(0x20BB7) ,
        return2:String.fromCharCode(0xD842, 0xDFB7)
    }

}
// console.log(first117());//abc
// console.log(first117());//{ return1: 'ஷ', return2: '𠮷' }

//5.7.3-1
/**
 * @param {string}str118
 * @remark String.prototype.length属性返回字符串的长度。
 */
function first118(str118) {
    return 'abc'.length
}
// console.log(first118());//3

//5.7.4-1
/**
 * @param {string}str119
 * @remark String.prototype.charAt()方法返回指定位置的字符。
 */
function first119(str119) {
    return 'abc'.charAt(1)
}
// console.log(first119());//b

//5.7.4-2
/**
 * @param {string}str120
 * @remark String.prototype.charCodeAt()方法返回指定位置字符的 Unicode 码点。
 */
function first120(str120) {
    return 'abc'.charCodeAt(1)
}

/**
 * @param {string}str120
 * @remark 如果没有任何参数，charCodeAt返回首字符的 Unicode 码点。
 * @remark 如果参数为负数，或大于等于字符串的长度，charCodeAt返回NaN。
 */
function first120(str120) {
    return {
        return1:'abc'.charCodeAt(-1),
        return2:'abc'.charCodeAt() 
    }
}
// console.log(first120());//98
// console.log(first120());//{ return1: NaN, return2: 97 }

//5.7.4-3
/**
 * @param {string}str121
 * @remark concat方法用于连接两个字符串，返回一个新字符串，不改变原字符串。
 * @remark 如果参数不是字符串，concat方法会将其先转为字符串，然后再连接。
 */
function first121(str121) {
    return 'abc'.concat(123)
}
// console.log(first121());//abc123

//5.7.4-4
/**
 * @param {string}str122
 * @remark slice方法用于提取字符串的一部分，返回一个新字符串，不改变原字符串。
 */
function first122(str122) {
    return 'abcde'.slice(1, 3)
}
// console.log(first122());//bc

//5.7.4-5
/**
 * @param {string}str123
 * @remark sbustring方法用于提取字符串的一部分，返回一个新字符串，不改变原字符串。
 */
function first123(str123) {
    return 'abcde'.substring(1, 3)
}   

/**
 * @param {string}str123
 * @remark 如果省略第二个参数，则表示子字符串一直到原字符串的结束。
 * @remark 如果第一个参数大于第二个参数，substring方法会自动更换两个参数的位置。
 * @remark 如果参数是负数，substring方法会自动将负数转为0。
 */
function first123(str123) {
    return {
        return1:'abcde'.substring(-1, 3),
        return2:'abcde'.substring(1, -1),//转0，并调换位置
        return3:'abcde'.substring(3)
    }
}
// console.log(first123());//bc
// console.log(first123());//{ return1: 'abc', return2: 'a', return3: 'de' }

//5.7.4-6
/**
 * @param {string}str124
 * @remark substr方法的第一个参数是子字符串的开始位置（从0开始计算），第二个参数是子字符串的长度。
 * @remark 如果省略第二个参数，则表示子字符串一直到原字符串的结束。
 * @remark 如果第一个参数是负数，表示倒数计算的字符位置。如果第二个参数是负数，将被自动转为0，因此会返回空字符串。
 */
function first124(str124) {
    return {
        return1:'abcde'.substr(1, 3),
        return2:'abcde'.substr(-1, 3),
        return3:'abcde'.substr(1, -1)//转0，表示长度为0
    }
}
// console.log(first124());//{ return1: 'bcd', return2: 'e', return3: '' }

//5.7.4-7
/**
 * @param {string}str125
 * @remark indexOf方法用于确定一个字符串在另一个字符串中第一次出现的位置，从0开始计算。
 * @remark indexOf方法还可以接受第二个参数，表示从该位置开始向后匹配。
 * @remark lastIndexOf方法的用法跟indexOf方法一致，主要的区别是lastIndexOf从尾部开始匹配，indexOf则是从头部开始匹配。
 * @remark lastIndexOf的第二个参数表示从该位置起向前匹配。
 */
function first125(str125) {
    return {
        return1:'abcde'.indexOf('b',2),
        return2:'abcde'.lastIndexOf('b',2)
    }
}
// console.log(first125());//{ return1: -1, return2: 1 }

//5.7.4-8
/**
 * @param {string}str126
 * @remark trim方法用于去除字符串两端的空格，返回一个新字符串，不改变原字符串。
 * @remark 该方法去除的不仅是空格，还包括制表符（\t、\v）、换行符（\n）和回车符（\r）
 */
function first126(str126) {
    return ' abc '.trim()
}
// console.log(first126());//abc

//5.7.4-9
/**
 * @param {string}str127
 * @remark toLowerCase方法用于将一个字符串全部转为小写。toUpperCase则是全部转为大写。它们都返回一个新字符串，不改变原字符串。
 */
function first127(str127) {
    return 'ABC'.toLowerCase()
}
// console.log(first127());//abc

//5.7.4-10
/**
 * @param {string}str128
 * @remark match方法用于确定原字符串是否匹配某个子字符串，返回一个数组，成员为匹配的第一个字符串。如果没有找到匹配，则返回null。
 * @remark 返回的数组还有index属性和input属性，分别表示匹配字符串开始的位置和原始字符串。
 */
function first128(str128) {
    return 'abc'.match('b')
}
// console.log(first128());//[ 'b', index: 1, input: 'abc', groups: undefined ]

//5.7.4-11
/**
 * @param {string}str129
 * @remark search方法用于确定原字符串是否包含某个子字符串，返回一个整数。没找到匹配，则返回-1。
 * @remark replce方法用于替换匹配的子字符串，一般情况下它的第一个参数可以是一个字符串，表示替换为哪个字符串；也可以是一个函数，表示动态
 */
function first129(str129) {
    return {
        return1:'abc'.replace('b', 'B'),
        return2:'cat, bat, sat, fat'.search('at')
    }
}
// console.log(first129());//{ return1: 'aBc', return2: 1 }

//5.7.4-12
/**
 * @param {string}str130
 * @remark split方法用于将一个字符串分割成多个子串，然后将结果以数组形式返回。
 * @remark 如果分割规则为空字符串，则返回数组的成员是原字符串的每一个字符。
 * @remark 如果满足分割规则的两个部分紧邻着（即两个分割符中间没有其他字符），则返回数组之中会有一个空字符串。
 * @remark split方法还可以接受第二个参数，限定返回数组的最大成员数。
 */
function first130(str130) {
    return {
        return1:'a-b-c'.split('-'),
        return2:'abc'.split('')
    }
}
// console.log(first130());//{ return1: [ 'a', 'b', 'c' ], return2: [ 'a', 'b', 'c' ] }

//5.7.4-13
/**
 * @param {string}str131
 * @remark localCompare 用于比较两个字符串。它返回一个整数，如果小于0，表示第一个字符串小于第二个字符串；如果等于0，表示两者相等；如果大于0，表示第一个字符串大于第二个字符串。
 * @remark 该方法可以接受第二个参数，指定所使用的语言（默认是英语），然后根据该语言的规则进行比较。
 */
function first131(str131) {
    return {
        return1:'a'.localCompare('b'),
        return2:'a'.localCompare('a')
    }
}
// console.log(first131());//{ return1: -1, return2: 0 }

//5.8.2-1
/**
 * @param {string}str132
 * @remark Math.abs方法返回参数值的绝对值
 */
function first132(str132) {
    return Math.abs(-1)
}
// console.log(first132());//1

//5.8.2-2
/**
 * @param {string}str133
 * @remark Math.max方法返回参数之中最大的那个值，Math.min返回最小的那个值。
 * @remark 如果参数为空, Math.min返回Infinity, Math.max返回-Infinity。
 */
function first133(str133) {
    return Math.max(1, 2)
}
// console.log(first133());//2

//5.8.2-3
/**
 * @param {string}str134
 * @remark Math.floor方法返回小于或等于参数值的最大整数（地板值）。
 * @remark Math.ceil方法返回大于或等于参数值的最小整数（天花板值）。
 */
function first134(str134) {
    function ToInteger(x) {
        x = Number(x);
        return x < 0 ? Math.ceil(x) : Math.floor(x);
    }
    return ToInteger(3.2)
}
// console.log(first134());//3

//5.8.2-4
/**
 * @param {string}str135
 * @remark Math.round方法返回参数值四舍五入后的整数。
 * @remark 注意，它对负数的处理（主要是对0.5的处理）。
 */
function first135(str135) {
    return Math.round(-3.5)
}
// console.log(first135());//-3

//5.8.2-5
/**
 * @param {string}str136
 * @remark Math.pow方法返回以第一个参数为底数、第二个参数为指数的幂运算值
 */
function first136(str136) {
    return Math.pow(2, 3)
}
// console.log(first136());//8

//5.8.2-6
/**
 * @param {string}str137
 * @remark Math.sqrt方法返回参数值的平方根。如果参数是一个负值，则返回NaN
 */
function first137(str137) {
    return Math.sqrt(4)
}
// console.log(first137());//2

//5.8.2-7
/**
 * @param {string}str138
 * @remark Math.log方法返回以e为底的自然对数值。
 */
function first138(str138) {
    return Math.log(Math.E)
}
// console.log(first138());//1

//5.8.2-8
/**
 * @param {string}str139
 * @remark Math.exp方法返回常数e的参数次方。
 */
function first139(str139) {
    return Math.exp(1)
}
// console.log(first139());//2.718281828459045

//5.8.2-9
/**
 * @param {string}str140
 * @remark Math.random方法返回0到1之间的随机数，包括0而排除1。
 */
function first140(str140) {
    return Math.random()
}
// console.log(first140());//0.22475748914115545

//5.8.2-10
/**
 * @param {string}str141
 * @remark Math对象还提供一系列三角函数方法。
 */
function first141(str141) {
    return Math.sin(Math.PI / 2)
}
// console.log(first141());//1

//5.9.1
/**
 * @param {string}str142
 * @remark Date对象可以作为普通函数直接调用，返回一个代表当前时间的字符串
 */
function first142(str142) {
    return Date()
}
// console.log(first142());//Mon Nov 27 2023 17:45:09 GMT+0800 (中国标准时间)

//5.9.2
/**
 * @param {string}str143
 * @remark Date对象可以作为构造函数调用，返回一个指定日期和时间的Date对象
 * @remark Date实例求值的时候，默认调用的是toString()方法
 */
function first143(str143) {
    var today = new Date()
    return today
}

/**
 * @param {string}str144
 * @remark 第一点，参数可以是负整数，代表1970年元旦之前的时间。
 * @remark 第二点，只要是能被Date.parse()方法解析的字符串，都可以当作参数。
 * @remark 参数为年、月、日等多个整数时，年和月是不能省略的，其他参数都可以省略的。
 */
function first144(str144) {
    var today = new Date('2023-11-27')
    return today
}

/**
 * @param {string}str145
 * @remark 参数如果超出了正常范围，会被自动折算。
 * @remark 参数还可以使用负数，表示扣去的时间。
 */
function first145(str145) { 
    return{
        return1:new Date(2013, 15),
        return2:new Date(2013, 15, -1)
    }
}
// console.log(first143());//2023-11-27T09:46:40.950Z
// console.log(first144());//2023-11-27T00:00:00.000Z
// console.log(first145());
/*
{
  return1: 2014-03-31T16:00:00.000Z,
  return2: 2014-03-29T16:00:00.000Z
}
*/

//5.9.3
/**
 * @param {string}str146
 * @remark 日期运算
 * @remark 两个日期实例对象进行减法运算时，返回的是它们间隔的毫秒数；进行加法运算时，返回的是两个字符串连接而成的新字符串。
 */
function first146(str146) {
    var d1 = new Date(2000, 2, 1);
    var d2 = new Date(2000, 3, 1);
    return{
        return1:d2 - d1,
        return2:d1 + d2
    } 
}
// console.log(first146());
/*
{
  return1: 2678400000,
  return2: 'Wed Mar 01 2000 00:00:00 GMT+0800 (中国标准时间)Sat Apr 01 2000 00:00:00 GMT+0800 (中国标准时间)'
}
*/

//5.9.4-1
/**
 * @param {string}str147
 * @remark Date.now方法返回当前时间距离时间零点（1970年1月1日 00:00:00 UTC）的毫秒数，相当于 Unix 时间戳乘以1000。
 */
function first147(str147) {
    return Date.now()
}
// console.log(first147());//1701079067578

//5.9.4-2
/**
 * @param {string}str148
 * @remark Date.parse方法用于将字符串解析为时间戳。
 */
function first148(str148) {
    return Date.parse('2013-11-27')
}
// console.log(first148());//1385510400000

//5.9.4-3
/**
 * @param {string}str149
 * @remark Date.UTC方法用于将日期字符串解析为时间戳。
 */
function first149(str149) {
    return Date.UTC(2013, 15)
}
// console.log(first149());//1385506800000

//5.9.5-1
/**
 * @param {string}str150
 * @remark valueOf方法返回实例对象距离时间零点（1970年1月1日00:00:00 UTC）对应的毫秒数，该方法等同于getTime方法。
 */
function first150(str150) {
    var today = new Date()
    return today.valueOf()
}
// console.log(first150());//1701082467579

//5.9.5-2
/**
 * @param {string}str151
 * @remark toString方法返回一个完整的日期字符串。
 */
function first151(str151) {
    var today = new Date()
    return today.toString()
}
// console.log(first151());//Mon Nov 27 2023 18:00:55 GMT+0800 (中国标准时间)

/**
 * @param {string}str152
 * @remark toUTCString方法返回一个完整的日期字符串，表示的是1970年1月1日00:00:0
 */
function first152(str152) {
    var today = new Date()
    return today.toUTCString()
}
// console.log(first152());//Mon, 27 Nov 2023 10:01:35 GMT

/**
 * @param {string}str153
 * @remark toDateString方法返回一个日期字符串，只包含年月日。
 */
function first153(str153) {
    var today = new Date()
    return today.toDateString()
}
// console.log(first153());//Mon Nov 27 2023

/**
 * @param {string}str154
 * @remark toTimeString方法返回一个时间字符串，只包含时分秒。
 */
function first154(str154) {
    var today = new Date()
    return today.toTimeString()
}
// console.log(first154());//18:02:36 GMT+0800 (中国标准时间)

/**
 * @param {string}str155
 * @remark toLocaleString方法返回一个日期字符串，根据本地时间格式表示。
 * @remark toLocaleDateString方法返回一个日期字符串，根据本地时间格式表示。
 * @remark toLocaleTimeString方法返回一个时间字符串，根据本地时间格式表示。
 */
function first155(str155) {
    var today = new Date()
    return{
        return1:today.toLocaleString(),
        return2:today.toLocaleDateString(),
        return3:today.toLocaleTimeString()
    }
}
// console.log(first155());
/*
{
  return1: '2023/11/27 18:04:33',
  return2: '2023/11/27',
  return3: '18:04:33'
}
*/

/**
 * @param {string}str156
 * @remark toJSON方法返回一个符合 JSON 格式的 ISO 日期字符串，与toISOString方法的返回结果完全相同。
 */
function first156(str156) {
    var today = new Date()
    return today.toJSON()
}
// console.log(first156());//"2023-11-27T10:04:5

//5.9.5-3
/**
 * @param {string}str157
 * @remark get方法
 */
function first157(str157) {
    var today = new Date()
    return{
        return1:today.getYear(),
        return2:today.getFullYear(),
        return3:today.getMonth(),
        return4:today.getDate(),
        return5:today.getDay(),
        return6:today.getHours(),
        return7:today.getMinutes(),
        return8:today.getSeconds(),
        return9:today.getMilliseconds(),
        return10:today.getUTCDate()
    }
}
// console.log(first157());
/*
{
  return1: 123,
  return2: 2023,
  return3: 10,
  return4: 27,
  return5: 1,
  return6: 18,
  return7: 8,
  return8: 16,
  return9: 730,
  return10: 27
}*/

//5.9.5-4
/**
 * @param {string}str158
 * @remark set方法
 */
function first158(str158) {
    var today = new Date()
    today.setYear(2013)
    return{
        return1:today.getYear(),
        return2:today.getFullYear(),
        return3:today.getMonth(),
        return4:today.getDate(),
        return5:today.getDay(),
        return6:today.getHours(),
        return7:today.getMinutes(),
        return8:today.getSeconds(),
        return9:today.getMilliseconds(),
        return10:today.getUTCDate()
    }
}
// console.log(first158());
/*
{
  return1: 113,
  return2: 2013,
  return3: 10,
  return4: 27,
  return5: 3,
  return6: 18,
  return7: 15,
  return8: 29,
  return9: 908,
  return10: 27
}*/

//5.10.2
/**
 * @param {string}str159
 * @remark 一类是修饰符相关，用于了解设置了什么修饰符。
 */
function first159 (str159){
    var r = /abc/igm;
    return{
        s1:r.ignoreCase,
        s2:r.global,
        s3:r.multiline,
        s4:r.flags 
    }
}

/**
 * @param {string}str160
 * @remark 另一类是与修饰符无关的属性
 */
function first160 (str160){
    var r = /abc/igm;
    return{
        s1:r.lastIndex ,
        s2:r.source 
    }
}
// console.log(first159())//{ s1: true, s2: true, s3: true, s4: 'gim' }
// console.log(first160())//{ s1: 0, s2: 'abc' }


//5.10.3-1
/**
 * @param {string}str161
 * @remark 正则实例对象的test方法返回一个布尔值，表示当前模式是否能匹配参数字符串。
 */
function first161 (str161){
    return /cat/.test('cats and dogs')
}

/**
 * @param {string}str162
 * @remark 如果正则表达式带有g修饰符，则每一次test方法都从上一次结束的位置开始向后匹配。
 * @remark 带有g修饰符时，可以通过正则对象的lastIndex属性指定开始搜索的位置。
 */
function first162 (str162){
    var r = /x/g;
    var s = '_x_x';

    r.lastIndex = 4;
    return r.test(s)
}

/**
 * @param {string}str163
 * @remark 注意，带有g修饰符时，正则表达式内部会记住上一次的lastIndex属性，这时不应该更换所要匹配的字符串，否则会有一些难以察觉的错误。
 */
function first163 (str163){
    var r = /bb/g;
    return {
        s1:r.test('bb') ,
        s2:r.test('-bb-')
    }
}

/**
 * @param {string} str164
 * @remark 如果正则模式是一个空字符串，则匹配所有字符串。
 */
function first164 (str164){
    return new RegExp('').test('abc')
}
// console.log(first161())//true
// console.log(first162())//false
// console.log(first163())//{ s1: true, s2: false }
// console.log(first164())//true

//5.10.3-2
/**
 * @param {string} str165
 * @remark 正则实例对象的exec()方法，用来返回匹配结果。如果发现匹配，就返回一个数组，成员是匹配成功的子字符串，否则返回null。
 * @remark 如果正则表示式包含圆括号（即含有“组匹配”），则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果，后面的成员就是圆括号对应的匹配成功的组。
 * @remark exec()方法的返回数组还包含以下两个属性---input：整个原字符串。index：模式匹配成功的开始位置（从0开始计数）
 */
function first165 (str165){
    var r = /(cat)s/;
    var s = 'cats and dogs';
    var m = r.exec(s);
    return m
}

/**
 * @param {string} str166
 * @remark 如果正则表达式加上g修饰符，则可以使用多次exec()方法，下一次搜索的位置从上一次匹配成功结束的位置开始。
 */
function first166 (str166){
    var r = /(cat)s/g;
    var s = 'cats and dogs';
    var m = r.exec(s);
    return m
}

/**
 * @param {string} str167
 * @remark 利用g修饰符允许多次匹配的特点，可以用一个循环完成全部匹配。
 */
function first167 (str167){
    var reg = /a/g;
    var str = 'abc_abc_abc'
    
    while(true) {
      var match = reg.exec(str);
      if (!match) break;
      console.log('#' + match.index + ':' + match[0]);
    }
}

// console.log(first165())//[ 'cats', 'cat', index: 0, input: 'cats and dogs', groups: undefined ]
// console.log(first166())//[ 'cats', 'cat', index: 0, input: 'cats and dogs', groups: undefined ]
// console.log(first167())
/*
#0:a
#4:a
#8:a
undefined
*/

//5.10.4
/**
 * @param {string} str168
 * @remark 字符串实例对象的match方法对字符串进行正则匹配，返回匹配结果。
 */
function first168 (str168){
    var s = '_x_y';
    var r1 = /x/;
    
    return s.match(r1) 
}

/**
 * @param {string} str169
 * @remark 如果正则表达式带有g修饰符，则该方法与正则对象的exec方法行为不同，会一次性返回所有匹配成功的结果。
 * @remark 两种结果形式不同在于，第二个加了groups属性，表示匹配成功的组。
 */
function first169 (str169){
    var s = '_x_x';
    var r = /x/g;
    
    return s.match(r)
}

/**
 * @param {string} str170
 * @remark 设置正则表达式的lastIndex属性，对match方法无效，匹配总是从字符串的第一个字符开始。
 */
function first170 (str170){
    var s = '_x_x';
    var r = /x/g;
    
    r.lastIndex = 4;
    return {
        s1:s.match(r),
        s2:r.lastIndex 
    }
}

// console.log(first168())//[ 'x', index: 1, input: '_x_x', groups: undefined ]
// console.log(first169())//[ 'x', 'x' ]
// console.log(first170())//{ s1: [ 'x', 'x' ], s2: 0 }

//5.10.4-2
/**
 * @param {string} str171
 * @remark 字符串实例对象的search方法与match方法类似，但它只返回第一个匹配成功的结果的位置，如果没有匹配成功，则返回-1。
 */
function first171 (str171){
    var s = '_x_x';
    var r = /x/;
    return s.search(r)
}
// console.log(first171())//1


//5.10.4-3
/**
 * @param {string} str172
 * @remark 字符串实例对象的replace方法，用于替换匹配的子字符串。
 * @remark replace方法的第一个参数是正则表达式，第二个参数是替换字符串。
 * @remark 如果正则表达式中有g修饰符，则replace方法会替换所有匹配的子字符串。
 */
function first172 (str172){
    var s = '_x_x';
    var r = /x/g;
    return s.replace(r, 'y')
}

/**
 * @param {string} str173
 * @remark replace方法的一个应用，就是消除字符串首尾两端的空格。
 */
function first173 (str173){
    var s =' abc  ';
    return s.replace(/^\s+|\s+$/g, '')
}

/**
 * @param {string} str174
 * @remark replace方法的第二个参数可以使用美元符号$，用来指代所替换的内容
 */
function first174 (str174){
    return 'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
}

/**
 * @param {string} str175
 * @remark replace方法的第二个参数还可以是一个函数，将每一个匹配内容替换为函数返回值。
 */
function first175 (str175){
    return '3 and 5'.replace(/[0-9]+/g, function (match) {
        return 2 * match;
      })
}
// console.log(first172())//_y_y
// console.log(first173())//abc
// console.log(first174())//world hello
// console.log(first175())//6 and 10

//5.10.4-4
/**
 * @param {string} str176
 * @remark 字符串对象的split方法按照正则规则分割字符串，返回一个由分割后的各个部分组成的数组。
 */
function first176 (str176){
    var s = 'a,b,c';
    return s.split(/,/,2)
}

/**
 * @param {string} str177
 * @remark 如果正则表达式带有括号，则括号匹配的部分也会作为数组成员返回。
 */
function first177 (str177){
    return 'aaa*a*'.split(/(a*)/)
}
// console.log(first176())//[ 'a', 'b' ]
// console.log(first177())//[ '', 'aaa', '*', 'a', '*' ]

//5.10.5-1
/**
 * @param {string} str178
 * @remark 如果在正则表达式之中，某个字符只表示它字面的含义（就像前面的a和b），那么它们就叫做“字面量字符”（literal characters）。
 * @remark 点字符（.）匹配除回车（\r）、换行(\n) 、行分隔符（\u2028）和段分隔符（\u2029）以外的所有字符。
 */
function first178 (str178){
    return /c.t/.test('cat')
}

/**
 * @param {string} str179
 * @remark 位置字符用来提示字符所处的位置，主要有两个字符。
 * @remark /^test/表示字符串必须以test开头。
 */
function first179 (str179){
    return /^test/.test('test123') 
}

/**
 * @param {string} str180
 * @remark 竖线符号（|）在正则表达式中表示“或关系”（OR）
 * @remark 正则表达式指定必须匹配11或22
 */
function first180 (str180){
    return /11|22/.test('911') 
}
// console.log(first178())//true
// console.log(first179())//true
// console.log(first180())//true

//5.10.5-2
/**
 * @param {string} str181
 * @remark 正则表达式中那些有特殊含义的元字符，如果要匹配它们本身，就需要在它们前面要加上反斜杠。比如要匹配+，就要写成\+
 * @remark RegExp作为构造函数，参数是一个字符串。但是，在字符串内部，反斜杠也是转义字符，所以它会先被反斜杠转义一次，然后再被正则表达式转义一次，因此需要两个反斜杠转义。
 */
function first181 (str181){
    return{
        a: /\+/.test('+'),
        b: (new RegExp('1\\+1')).test('1+1')
    } 
}
// console.log(first181())//{ a: true, b: true }

//5.10.5-4
/**
 * @param {string} str182
 * @remark 字符类（class）表示有一系列字符可供选择，只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内
 * @remark 如果方括号内的第一个字符是[^]，则表示除了字符类之中的字符，其他字符都可以匹配
 * @remark 字符串bbc news包含a、b、c以外的其他字符，所以返回true；
*/
function first182 (str182){
    return /[^abc]/.test('bbc news')
}

/**
 * @param {string} str183
 * @remark 如果方括号内没有其他字符，即只有[^]，就表示匹配一切字符，其中包括换行符。相比之下，点号作为元字符（.）是不包括换行符的。
 */
function first183 (str183){
    var s = 'Please yes\nmake my day!';

    return{
        a:s.match(/yes.*day/),
        b:s.match(/yes[^]*day/)
    }
}

/**
 * @param {string} str184
 * @remark 某些情况下，对于连续序列的字符，连字符（-）用来提供简写形式，表示字符的连续范围。
 */
function first184 (str184){
    return /[0-9]/.test('1')
}

// console.log(first182())//true
// console.log(first183())
/*
{
  a: null,
  b: [
    'yes\nmake my day',
    index: 7,
    input: 'Please yes\nmake my day!',
    groups: undefined
  ]
}*/
// console.log(first184())//true


//5.10.5-5
/**
 * @param {string} str185
 * @remark 预定义模式指的是某些常见模式的简写方式。
 */
function first185 (str185){
    return /^\d{3}-\d{2}-\d{4}$/.test('123-45-6789')
}

/**
 * @param {string}str186
 * @remark 通常，正则表达式遇到换行符（\n）就会停止匹配。
 */
function first186 (str186){
    var html = "<b>Hello</b>\n<i>world!</i>";

   return  /.*/.exec(html)[0]
}
// console.log(first185())//true
// console.log(first186())//<b>Hello</b>

//5.10.5-6
/**
 * @param {string} str187
 * @remark 模式的精确匹配次数，使用大括号（{}）表示。{n}表示恰好重复n次，{n,}表示至少重复n次，{n,m}表示重复不少于n次，不多于m次。
 */
function first187 (str187){
    return /lo{2}k/.test('look') 
}
// console.log(first187())//true

//5.10.5-7
/**
 * @param {string} str188
 * @remark 量词符用来设定某个模式出现的次数。
 */
function first188 (str188){
    return /a{3,5}/.test('aaa')
}
// console.log(first188())//true

//5.10.5-8
/**
 * @param {string} str189
 * @remark 默认情况下都是最大可能匹配，即匹配到下一个字符不满足匹配规则为止。这被称为贪婪模式。
 */
function first189 (str189){
    return /a+/.exec('aaaaa')
}

/**
 * @param {string} str190
 * @remark 还有非贪婪模式，即最小可能匹配。只要一发现匹配，就返回结果，不要往下检查。如果想将贪婪模式改为非贪婪模式，可以在量词符后面加一个问号。
 */
function first190 (str190){
    return /a+?/.exec('aaaaa')
}
// console.log(first189())//[ 'aaaaa', index: 0, input: 'aaaaa', groups: undefined ]
// console.log(first190())//[ 'a', index: 0, input: 'aaaaa', groups: undefined ]


//5.10.9
/**
 * @param {string} str191
 * @remark 修饰符可以单个使用，也可以多个一起使用。
 * @remark 默认情况下，第一次匹配成功后，正则对象就停止向下匹配了。g修饰符表示全局匹配（global），加上它以后，正则对象将匹配全部符合条件的结果，主要用于搜索和替换。
 * @remark 正则模式不含g修饰符，每次都是从字符串头部开始匹配
 */
function first191 (str191){
    var regex = /b/;
    var str = 'abba';
    
    return regex.test(str);
}

/**
 * @param {string} str192
 * @remark i:默认情况下，正则对象区分字母的大小写，加上i修饰符以后表示忽略大小写（ignoreCase）。
 */
function first192 (str192){
    var regex = /b/i;
    var str = 'abba';
    
    return regex.test(str);
}

/**
 * @param {string} str193
 * @remark m修饰符表示多行模式（multiline），会修改^和$的行为。
 */
function first193 (str193){
    var regex = /b/m;
    var str = 'abba\nababa';
    
    return regex.test(str);
}

// console.log(first191())//true
// console.log(first192())//true
// console.log(first193())//true

//5.10.10
/**
 * @param {string} str194
 * @remark 正则表达式的括号表示分组匹配，括号中的模式可以用来匹配分组的内容。
 * @remark 有括号，结果+就表示匹配fred这个词。
 */
function first194 (str194){
    return /(fred)+/.test('fredfred')
}
console.log(first194())
